"""CompoGameLoader

    @author: Collin "Keeyai" Green

    @url: U{http://keeyai.com/projects-and-releases/compo-game-loader/}
    @version: 1.0.2
    @summary: The Competition Game Loader is a program to facilitate the playing
    and judging of games in the frequent online game development competitions like
    PyWeek and LudumDare. CGL will attempt to run any of the submissions, but authors
    can quickly and easily create make their game work specifically with CGL by creating
    a simple config file.

    @note: GUI generated with wxGlade
    @note: built with wxPython 2.8.8.0
    @dependencies: python, wxPython

    @license:
        MIT License

        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:

        The above copyright notice and this permission notice shall be included in
        all copies or substantial portions of the Software.

        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
        THE SOFTWARE.

    @change: 1.0.2
            - fixed a bug with files with spaces -- was wrapping in quotes before changing the CWD and os.path.dirname botches the quotes
    @change: 1.0.1
            - wrapped the game thread in try-except
            - stripped the game entry point before wrapping it in quotes, just in case
            - wrapped up some thumbnail code that threw someone an error
    @change: 1.0.0
            - added a block to not load new games until current one is unzipping
            - removed double click event - with single click added it isnt necessary or helpful
            - added a block to not load new games while current one is playing
    @change: beta.1.1
            - single clicking now selects games from the list
            - removed python pre-pend - in exes it does itself instead of python!
    @change: beta.1.0
            - tweaked some log stuff to clean up \\n's on linux
            - fixed thumbnail loading bugs -- wasn't correctly loading thumbs from config file
            - added some debug info
            - added command line verbosity options
    @change: beta.0.10
            - CGL now checks if a new version of the application is available and prompts the user to download it
            - Changed the gameinfo.xml files to allow multiple entry points, labels, and web links
            - Includes support for pre-beta.0.10 entry points
            - Fixed permission bug introduced with threading the games
            - Added funtionality to automatically prepend .py files with the path to python on the system - hopefully its a good thing :)
            - Changed the wildcard for 'All Files' in the entry point browser so it now matches extension-less files
    @change: beta.0.9
            - CGL now sets the CWD to the game folder to help run games that use relative paths
            - CGL auto searches for thumbnails, then tries screenshots - puts what it finds in the game list
            - Added thumbnail option to gameinfo.xml
            - CGL now opens directory prompt at first start up to avoid confusion
            - The overview pane is now auto-selected when you load a game
            - Fixed spelling error in about text
            - CGL config file now xml for more options
            - CGL no longer crashes without write permission in its folder or the game folder
            - CGL now saves user notes across sessions (creates another file in the cgl folder)
            - Fixed a bug failing to load screenshots in extracted zips
            - Fixed a bug with outputting errors to the console when parsing gameinfo.xml
            - CGL now searches for images and sticks them in the screenshots tab if none are specified
            - CGL now checks if the game thread is still running and makes you close that first (will stop thread from blocking temp dir cleanup)
            - Stopped CGL from popping up an error when an author puts a bad screenshot path in gameinfo.xml
    @change: beta.0.8 - added support for zips that dont have a container folder
    @change: beta.0.7
            - added threading so GUI works during gameplay
            - added license tab and auto-searching for license
            - added note tab for user notes during judging
            - added optional readmepath and licensepath to gameinfo.xml files
            - fixed bug in readme display - now properly scrolls to the top
            - coerced files into latin-1... hopefully that stops any unicode errors
    @change: beta.0.6 - fixed bug in mark as unplayed -- apparently didnt test after adding the logging
    @change: beta.0.5
            - fixed bug - wasn't checking for capital extensions on readme
            - narrowed the wordwrap on the about page so linux wx plays more nicely
            - tweaked makeexe.py pretty hard so it works on linux too
            - cleaned up the console output linebreaks
            - fixed bug in temp folder clean up code
            - fixed bug where popen's magic CWD change was preventing tempdir removal
            - added button to mark a game as un-played
    @change: beta.0.4 - tweaked version code to work with makeexe.py
    @change: beta.0.3 - Added path sanitizing for user supplied paths like entry points and screenshots
    @change: beta.0.2 - Added icon to window and about pane
    @change: beta.0.1 - If path to executable has spaces in it, CGL automatically wraps it in quotes
"""

VERSION = '1.0.2'


try:
    import sys, os, shutil, zipfile, subprocess, tempfile, textwrap, threading, time, urllib2, stat, getopt
    import wx, wx.lib.scrolledpanel as scrolled, wx.html as html
    import kml_single

except ImportError, err:
    print "Failed to Load Module: %s" % (err)
    sys.exit(1)


class MyHtmlListBox(wx.HtmlListBox):
    """A hacked together htmlListBox that looks a bit like a ListBox
    from the outside. This isn't a good way to handle this. Only works
    for this specific situation."""
    def __init__(self, parent, wid, choices, *args, **kwargs):
        wx.HtmlListBox.__init__(self, parent, wid, *args, **kwargs)

        self.items = []
        self.styles = {}
        self.thumbnails = {}

        self.InsertItems(choices,0)

    def InsertItems(self, items, index):
        for thing in items:
            # if 2tuple, add styles
            if type(thing) == type(()) and len(thing) == 2:
                self.items.append(thing[0])
                self.styles[ len(self.items)-1] = thing[1]

            # 3 tuple, add style and thumbnail
            elif type(thing) == type(()) and len(thing) == 3:
                self.items.append(thing[0])
                self.styles [ len(self.items)-1] = thing[1]
                self.thumbnails[ len(self.items)-1] = thing[2]

            # otherwise, use default
            elif thing != '':
                self.items.append(thing)

        self.SetItemCount(len(self.items))
        self.RefreshAll()

    def OnGetItem(self, n):

        if n in self.styles.keys():
            name = "<font color='%s'><b>%s</b></font>" % (self.styles[n], self.items[n] )
        else:
            return self.items[n]

        if n in self.thumbnails.keys():
            thumb = "<img src='%s' width='64' height='64' /><br />" % self.thumbnails[n]
        else:
            thumb = ''

        return "%s%s<hr />" % (thumb, name)

    def GetStringSelection(self):
        return self.items[ self.GetSelection()]

    def SetColor(self, index, color):
        self.styles[index] = color

    def Clear(self):
        wx.HtmlListBox.Clear(self)
        self.items = []
        self.styles = {}
        self.SetItemCount(0)


class MainFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MainFrame.__init__
        kwds["style"] = wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.notebook_1 = wx.Notebook(self, -1, style=0)
        self.readmepane = wx.Panel(self.notebook_1, -1)
        self.licensepane = wx.Panel(self.notebook_1, -1)
        self.notepane = wx.Panel(self.notebook_1, -1)
        self.overviewpane = wx.Panel(self.notebook_1, -1)
        self.overviewtext = wx.TextCtrl(self.overviewpane, -1, "", style=wx.TE_READONLY|wx.TE_MULTILINE|wx.TE_AUTO_URL|wx.TE_RICH2)
        self.playbutton = wx.Button(self.overviewpane, -1, "Play")
        self.unplayedbutton = wx.Button(self.overviewpane, -1, "Mark as Un-Played")
        self.readmetext = wx.TextCtrl(self.readmepane, -1, "", style=wx.TE_READONLY|wx.TE_MULTILINE|wx.TE_AUTO_URL)
        self.licensetext = wx.TextCtrl(self.licensepane, -1, "", style=wx.TE_READONLY|wx.TE_MULTILINE|wx.TE_AUTO_URL)
        self.notetext = wx.TextCtrl(self.notepane, -1, "", style=wx.TE_MULTILINE|wx.TE_AUTO_URL)
        self.savenotebutton = wx.Button(self.notepane, -1, "Save Notes")
        #self.screenshotspane = wx.Panel(self.notebook_1, -1)
        self.screenshotspane = scrolled.ScrolledPanel(self.notebook_1, -1)
        self.dependenciespane = wx.Panel(self.notebook_1, -1)
        self.dependenciestext = wx.TextCtrl(self.dependenciespane, -1, "", style=wx.TE_READONLY|wx.TE_MULTILINE|wx.TE_AUTO_URL|wx.TE_RICH2)
##        self.judgingpane = wx.Panel(self.notebook_1, -1)
##        self.judgingwindow = html.HtmlWindow(self.judgingpane, -1)
        self.consoletext = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_RICH2)
##        self.gamelist = wx.ListBox(self, -1, choices=["Select a Game Directory"], style=wx.LB_SINGLE)
        self.gamelist = MyHtmlListBox(self, -1, choices=["Select a Game Directory"], style=wx.LB_SINGLE)

        # Menu Bar
        self.mainFrame_menubar = wx.MenuBar()
        self.filemenu = wx.Menu()
        self.gamedirmenu = wx.MenuItem(self.filemenu, wx.NewId(), "Set Game Directory", "Click to set the current game directory", wx.ITEM_NORMAL)
        self.filemenu.AppendItem(self.gamedirmenu)
        self.mainFrame_menubar.Append(self.filemenu, "File")
        self.helpmenu = wx.Menu()
        self.aboutmenu = wx.MenuItem(self.helpmenu, wx.NewId(), "About", "Information about the application", wx.ITEM_NORMAL)
        self.helpmenu.AppendItem(self.aboutmenu)
        self.mainFrame_menubar.Append(self.helpmenu, "Help")
        self.SetMenuBar(self.mainFrame_menubar)
        # Menu Bar end

        self.__set_properties()
        self.__do_layout()

        self.Bind(wx.EVT_BUTTON, self.playButtonClick, self.playbutton)
        self.Bind(wx.EVT_BUTTON, self.unplayButtonClick, self.unplayedbutton)
        self.Bind(wx.EVT_BUTTON, self.saveNoteButtonClick, self.savenotebutton)
        self.Bind(wx.EVT_LISTBOX, self.gameDoubleClick, self.gamelist)
        #self.Bind(wx.EVT_LISTBOX_DCLICK, self.gameDoubleClick, self.gamelist)
        self.Bind(wx.EVT_MENU, self.gameDirMenuClicked, self.gamedirmenu)
        self.Bind(wx.EVT_MENU, self.aboutMenuClick, self.aboutmenu)

        self.overviewtext.Bind(wx.EVT_TEXT_URL, self.OnURL)
        self.readmetext.Bind(wx.EVT_TEXT_URL, self.OnURL)
        self.licensetext.Bind(wx.EVT_TEXT_URL, self.OnURL)
        self.notetext.Bind(wx.EVT_TEXT_URL, self.OnURL)
        self.dependenciestext.Bind(wx.EVT_TEXT_URL, self.OnURL)

        # end wxGlade

        # bind OnClose method
        self.Bind(wx.EVT_CLOSE, self.OnClose)

        # program info
        self.VERSION = VERSION
        self.VERSIONCHECKPATH = 'http://keeyai.com/versioncheck.php?application=cgl'
        self.UPDATECHECKFREQUENCY = 604800      # 1 week, in seconds

        self.THUMBWIDTH = 64                    # height of thumbnails
        self.THUMBHEIGHT = 64                   # width of thumbnails
        self.CONFIGFILE = 'gameinfo.xml'        # name of config file in games
        self.CGLCONFIGFILE = 'cgl.cfg'          # name of CGL config file
        self.DIRCONFIGFILE = 'cgl.info'         # name of directory cgl file
        self.CGLNOTEFILE = 'cglnotes.txt'          # name of notes file
        self.tempdir = ''
        self.thumbdir = tempfile.mkdtemp(prefix='cgl_')
        self.configtree = kml_single.KTree()    # config options
        self.isunzipping = False                # if currently unzipping
        self.startedunzipping = time.time()
        self.UNZIPWAITTIME = 3.0
        self.gameindex = -1                     # index of current game in game list

        # game info
        self.games = {}             # dict of games
        self.gamename = ''          # name of current game
        self.gamepath = ''          # path to current game folder
        self.gameentrypoint = ''     # path to current game entry point
        self.currentdir = ''        # current game directory
        self.thread = None          # thread to run

        # display info
        self.DEFAULTCOLOR = 'BLACK'
        self.PLAYEDCOLOR = 'GRAY'

        # verbosity stuffs
        self.ERROR = 1
        self.WARNING = 2
        self.INFO = 3
        self.VERBOSE = 4
        self.DEBUG = 5
        self.verbosity = self.INFO

        # get verbosity from command line
        try:
            optlist, args = getopt.getopt(sys.argv[1:], 'vq')
            for opt in optlist:
                if opt[0] == '-v':
                    self.verbosity = max(0, min( 5, self.verbosity + 1) )
                elif opt[0] == '-q':
                    self.verbosity = max(0, min( 5, self.verbosity - 1) )

            if self.verbosity == self.VERBOSE:
                self.message("Verbosity set to: VERBOSE", self.VERBOSE)
            elif self.verbosity == self.DEBUG:
                self.message("Verbosity set to: DEBUG", self.DEBUG)
        except:
            pass

        # disable unplay button until we load a directory
        self.unplayedbutton.Disable()

        # disable play button
        self.playbutton.Disable()

        # load config file
        self.cglpath = os.path.dirname( os.path.abspath(sys.argv[0]))

        if os.path.isfile( os.path.join( self.cglpath, self.CGLCONFIGFILE)):
            self.parseConfig(os.path.join( self.cglpath, self.CGLCONFIGFILE))

            cglinfo = self.configtree.root.cgl

            # load notes
            self.loadNotes()

            # if it has a directory
            if cglinfo.hasChild('lastdirectory'):
                lastdir = cglinfo.lastdirectory.getValue()

                # if directory is valid, open it
                if lastdir != '' and os.path.isdir( lastdir ):
                    self.loadDirectory(lastdir)
                # otherwise, show directory browser
                else:
                    self.loadDirBrowser()
            else:
                self.loadDirBrowser()

        else:
            self.configtree = self.writeDefaultConfig( os.path.join( self.cglpath, self.CGLCONFIGFILE))

            # load directory right off if this is first time running
            self.loadDirBrowser()

        # set icon
        if os.path.isfile( os.path.join(self.cglpath, 'cgl-logo.ico')):
            self.SetIcon(wx.Icon(os.path.join(self.cglpath, 'cgl-logo.ico'), wx.BITMAP_TYPE_ICO))

        # set default note content
        #self.notetext.AppendText("This is for your notes.")


        self.checkVersion()

    def __set_properties(self):
        # begin wxGlade: MainFrame.__set_properties
        self.SetTitle("Compo Game Loader - By Keeyai")
        self.SetSize((809, 633))
        self.screenshotspane.SetMinSize((450, 411))
        self.consoletext.SetMinSize((-1,150))
        self.gamelist.SetMinSize((300, -1))
        # end wxGlade

    def __do_layout(self):
        # begin wxGlade: MainFrame.__do_layout
        grid_sizer_1 = wx.FlexGridSizer(1, 2, 0, 0)
        grid_sizer_2 = wx.FlexGridSizer(2, 1, 0, 0)
        grid_sizer_3 = wx.FlexGridSizer(1,1,0,0)
        sizer_1_copy = wx.FlexGridSizer(1, 1, 0, 0)
        sizer_1 = wx.FlexGridSizer(1, 1, 0, 0)
        sizer_1.Add(self.overviewtext, 0, wx.EXPAND, 0)
        buttonsizer = wx.FlexGridSizer(1, 2, 2, 0)
        buttonsizer.Add(self.playbutton, 0, wx.ALIGN_LEFT, 0)
        buttonsizer.Add(self.unplayedbutton, 1, wx.ALIGN_RIGHT, 0)
        buttonsizer.AddGrowableCol(0)
        buttonsizer.AddGrowableCol(1)
        sizer_1.Add(buttonsizer)
        self.overviewpane.SetSizer(sizer_1)
        sizer_1.AddGrowableRow(0)
        sizer_1.AddGrowableCol(0)
        sizer_1_copy.Add(self.readmetext, 0, wx.EXPAND, 0)
        self.readmepane.SetSizer(sizer_1_copy)
        licensesizer = wx.FlexGridSizer(1,1,0,0)
        licensesizer.AddGrowableRow(0)
        licensesizer.AddGrowableCol(0)
        licensesizer.Add(self.licensetext, 0, wx.EXPAND, 0)
        self.licensepane.SetSizer(licensesizer)
        notesizer = wx.FlexGridSizer(2,1,0,0)
        notesizer.AddGrowableRow(0)
        notesizer.AddGrowableCol(0)
        notesizer.Add(self.notetext, 0, wx.EXPAND, 0)
        notebuttonsizer = wx.FlexGridSizer(1,1,0,0)
        notebuttonsizer.AddGrowableRow(0)
        notebuttonsizer.AddGrowableCol(0)
        notebuttonsizer.Add(self.savenotebutton)
        notesizer.Add(notebuttonsizer)
        self.notepane.SetSizer(notesizer)
        sizer_1_copy.AddGrowableRow(0)
        sizer_1_copy.AddGrowableCol(0)
        grid_sizer_3.AddGrowableRow(0)
        grid_sizer_3.AddGrowableCol(0)
        grid_sizer_3.Add(self.dependenciestext, 0, wx.EXPAND, 0)
        self.dependenciespane.SetSizer(grid_sizer_3)
        self.notebook_1.AddPage(self.overviewpane, "Overview")
        self.notebook_1.AddPage(self.readmepane, "Readme")
        self.notebook_1.AddPage(self.licensepane, "License")
        self.notebook_1.AddPage(self.screenshotspane, "Screenshots")
        self.notebook_1.AddPage(self.dependenciespane, "Dependencies")
        self.notebook_1.AddPage(self.notepane, "Notes")
##        self.notebook_1.AddPage(self.judgingpane, "Judging")
        grid_sizer_2.Add(self.notebook_1, 1, wx.EXPAND, 0)
        grid_sizer_2.Add(self.consoletext, 0, wx.EXPAND|wx.FIXED_MINSIZE, 0)
        grid_sizer_2.AddGrowableRow(0)
        grid_sizer_2.AddGrowableRow(1)
        grid_sizer_2.AddGrowableCol(0)
        grid_sizer_1.Add(grid_sizer_2, 1, wx.EXPAND, 0)
        grid_sizer_1.Add(self.gamelist, 0, wx.EXPAND|wx.FIXED_MINSIZE, 0)
        self.SetSizer(grid_sizer_1)
        grid_sizer_1.AddGrowableRow(0)
        grid_sizer_1.AddGrowableCol(0)
        grid_sizer_1.AddGrowableCol(1)
        self.Layout()
        # end wxGlade

    def OnClose(self, event):
        """Called on closing. Deletes the temporary directory."""

        # dont close if a game is running in a thread -- so we can clean up temp dir
        if self.thread is not None:
            if self.thread.isAlive():
                self.writeLog("Please close the running game before closing CGL")
                return

        # delete temp dir
        try:
            if self.tempdir != '':
                if os.path.exists(self.tempdir) and os.path.isdir(self.tempdir):
                    shutil.rmtree(self.tempdir)
        except Exception, e:
            self.message( "Failed to delete temp file: %s" % self.tempdir, self.ERROR)
            self.message(repr(e), self.ERROR)

        # delete thumb dir
        try:
            if self.thumbdir != '':
                if os.path.exists(self.thumbdir) and os.path.isdir(self.thumbdir):
                    shutil.rmtree(self.thumbdir)
        except Exception, e:
            self.message( "Failed to delete temp file: %s" % self.thumbdir, self.ERROR)
            self.message(repr(e), self.ERROR)

        event.Skip()

    def parseConfig(self, configpath):

        # parse config into KML
        kml = kml_single.KML()
        tree = kml.parseFile( configpath)

        try:
            settings = tree.root.cgl

            if (settings.hasChild('lastdirectory') and
                settings.hasChild('lastupdate')):

                self.configtree = tree

            else:
                self.message("Config file invalid. Overwriting with defaults.", self.ERROR)
                return self.writeDefaultConfig(configpath)

        except:
            self.message("Config file invalid. Overwriting with defaults.", self.ERROR)

            # invalid config file - reset it
            self.configtree = self.writeDefaultConfig(configpath)

    def saveConfig(self):
        try:
            if self.configtree is not None:
                kml = kml_single.KML()
                kml.writeXML(self.configtree, os.path.join( self.cglpath, self.CGLCONFIGFILE))
        except Exception, e:
            self.message('Failed to save CGL config information', self.ERROR)
            self.message( repr(e), self.ERROR)

    def writeDefaultConfig(self, configpath):

        kml = kml_single.KML()

        tree = kml_single.KTree()
        tree.addNode('cgl')
        tree.addNode('lastdirectory','')
        tree.moveUp()
        tree.addNode('lastupdate', time.strftime('%Y-%m-%d'))

        kml.writeXML(tree, configpath)

        return tree

    def playButtonClick(self, event):
        self.playGame()

    def unplayButtonClick(self, event):
        # get list selection, if any
        index = self.gamelist.GetSelection()
        gamename = self.gamelist.GetStringSelection()

        if index >= 0:
            # show message to user
            self.writeLog("Marking %s as Un-Played" % gamename)

            # change color in list
            self.gamelist.SetColor(index, self.DEFAULTCOLOR)

            # remove from directory file
            if os.path.isfile( os.path.join(self.currentdir, self.DIRCONFIGFILE)):
                playedgames = file(os.path.join(self.currentdir, self.DIRCONFIGFILE),'r+').readlines()
                dirconfig = file(os.path.join(self.currentdir, self.DIRCONFIGFILE),'w+')
                for game in playedgames:
                    if game.strip() != gamename.strip():
                        dirconfig.write( "%s\n"%game )
                dirconfig.close()

    def saveNoteButtonClick(self, event):
        self.saveNotes()

    def saveNotes(self):
        # get notes content
        content = self.notetext.GetValue()

        try:
            notefile = open( os.path.join( self.cglpath, self.CGLNOTEFILE), 'w+')
            content = self.notetext.GetValue()

            # encode so it all plays nice
            content = content.encode('latin-1')
            notefile.write( content)

            # leave message
            self.writeLog('Saved Notes')

        except Exception, e:
            self.message('Failed to Save Notes', self.ERROR)
            self.message( repr(e), self.ERROR)

            self.writeLog('Failed to Save Notes', 'error')
            self.writeLog( repr(e), 'error')

    def loadNotes(self):
        try:
            notefile = open( os.path.join( self.cglpath, self.CGLNOTEFILE))

            # add notes to note text area
            self.notetext.AppendText(notefile.read())

        except:
            self.notetext.AppendText("This is for your notes")

    def gameDoubleClick(self, event):
        gamename = self.gamelist.GetStringSelection()

        if gamename != '':
            self.loadGame(gamename)

    def gameClick(self, event): # wxGlade: MainFrame.<event_handler>
        pass

    def gameDirMenuClicked(self, event):
        self.loadDirBrowser()

    def loadDirBrowser(self):
        # show dir dialog
        dlg = wx.DirDialog(
            self, message="Select the game directory",
          #  defaultPath= self.projectrootdir.GetValue(),
            style=wx.DD_DEFAULT_STYLE | wx.DD_DIR_MUST_EXIST | wx.CHANGE_DIR
            )

        # Show the dialog and retrieve the user response.
        if dlg.ShowModal() == wx.ID_OK:
            # load directory
            self.loadDirectory(dlg.GetPath())

        # Destroy the dialog.
        dlg.Destroy()

    def aboutMenuClick(self, event):
      # from wxpython demo, as usual
      # First we create and fill the info object
        info = wx.AboutDialogInfo()
        info.Name = "Compo Game Loader"
        info.Version = self.VERSION
        info.Copyright = '(C) 2008 Collin "Keeyai" Green'

        description = """The Competition Game Loader (CGL) is meant to make
judging game development competitions easy. Generally there are a
great number of entries, and each participant needs to run and
judge each of the other games. Each game has its own dependencies,
instructions, and notes, and going through all of this for every game
is tedious at best and infuriating at worst. CGL aims to eliminate the
pain by providing a common interface to all the games, keeping track
of which ones you have played already, and including helpful information
like screenshots, developer information, and a link to the game's
judging page."""

        info.Description = '\n'.join(textwrap.wrap(description, 50))
        info.WebSite = ("http://keeyai.com/projects-and-releases/compo-game-loader/", "CGL Home Page")
        info.Developers = [ 'Collin "Keeyai" Green']

        if os.path.isfile( os.path.join( self.cglpath, 'license.txt')):
            licensefile = file(os.path.join( self.cglpath, 'license.txt'), 'r')
            licensetext = licensefile.read()
            licensefile.close()
        else:
            licensetext = 'License file not found. Please contact the developers for a copy of the license.'

        licensetext.replace('\n', ' ')
        info.License = '\n'.join(textwrap.wrap(licensetext,70))

        # Then we call wx.AboutBox giving it that info object
        wx.AboutBox(info)

    def loadDirectory(self, directory):
        """Loads all the 'games' in the specified directory"""

        # check if directory exists and is a directory
        if not os.path.isdir(directory):
            self.message("%s is not a valid directory"%directory, self.ERROR)
            return False

        self.writeLog("Loading Directory: %s"%directory, 'normal')
        self.message("Loading Directory: %s"%directory, self.DEBUG)

        # disable play button
        self.playbutton.Disable()

        # enable un-play button
        self.unplayedbutton.Enable()

        # save dir info
        self.currentdir = directory

        # save new directory into config file
        self.configtree.root.cgl.lastdirectory.setValue(directory)
        self.saveConfig()

        # load directory config if available
        if os.path.isfile( os.path.join(directory, self.DIRCONFIGFILE)):
            dirconfig = file(os.path.join(directory, self.DIRCONFIGFILE),'a+')
            tempgames = dirconfig.readlines()
            playedgames = []
            for g in tempgames:
                playedgames.append(g.strip())
            dirconfig.close()
        else:
            try:
                dirconfig = file(os.path.join(directory, self.DIRCONFIGFILE),'a+')
                dirconfig.close()
            except Exception, e:
                self.message("Failed to create directory config in %d" % directory, self.ERROR)
                self.message(repr(e), self.ERROR)

            playedgames = []

        # empty games dict and list
        self.games = {}
        self.gamelist.Clear()
        imageindex = {}

        # create temp directory if necessary
        if self.tempdir == '':
            self.tempdir = tempfile.mkdtemp(prefix='cgl_')

        # delete files inside if already exists
        else:
            for f in os.listdir(self.tempdir):
                if os.path.isfile( os.path.join(self.tempdir, f)):
                    os.remove(os.path.join(self.tempdir, f))
                elif os.path.isdir( os.path.join(self.tempdir, f)):
                    shutil.rmtree(os.path.join(self.tempdir, f))

        # clear thumb directory
        if self.thumbdir == '':
            self.thumbdir = tempfile.mkdtemp(prefix='cgl_')
        else:
            for f in os.listdir(self.thumbdir):
                if os.path.isfile( os.path.join(self.thumbdir, f)):
                    os.remove(os.path.join(self.thumbdir, f))
                elif os.path.isdir( os.path.join(self.thumbdir, f)):
                    shutil.rmtree(os.path.join(self.thumbdir, f))

        # get files in directory
        files = os.listdir(directory)

        # add nodes to tree
        for f in files:

            # skip config file
            if f == self.DIRCONFIGFILE:
                continue

            # if directory
            if os.path.isdir(os.path.join(directory, f)):

                # save game location
                self.games[f] = os.path.join(directory, f)

                self.message('Adding %s to games list' % f, self.VERBOSE)

                thumb = self.findThumbnail(os.path.join(directory, f))
                if thumb:
                    imageindex[f] = thumb.SaveFile( os.path.join(self.thumbdir, f), wx.BITMAP_TYPE_JPEG)

            # if file
            else:

                # if zip file
                if f.endswith( ('.zip', '.ZIP') ) and zipfile.is_zipfile(os.path.join(directory,f)):

                    # open zip for reading
                    myzip = zipfile.ZipFile(os.path.join(directory,f), 'r')

                    # test the zip
                    if myzip.testzip() is not None:
                        self.message('Error: %s is corrupted. Try extracting yourself' % f, self.ERROR)

                    else:
                        # use zip name for game, without the .zip
                        gamename = f[:-4]
                        self.games[gamename] = os.path.join(directory, f)
                        self.message('Adding %s to games list (zipped)' % (f), self.VERBOSE)

                        # unzip config and all image files... need to read config to check if
                        # thumbnail is in a different folder... sigh
                        namelist = myzip.namelist()
                        thumbnailfound = False
                        # look for config file
                        configpath = ''
                        for fi in namelist:
                            path = os.path.dirname(fi)
                            filename = os.path.basename(fi)

                            if filename == self.CONFIGFILE:
                                configpath = fi
                                break

                        # has config file
                        if configpath != '':
                            try:
                                # extract config file
                                outfile = open(os.path.join(self.tempdir, self.CONFIGFILE), 'wb')
                                outfile.write(myzip.read(configpath))
                                outfile.close()

                                # read config file
                                kml = kml_single.KML()
                                configfile = open( os.path.join(self.tempdir, self.CONFIGFILE))
                                configcontents = configfile.read()
                                tree = kml.parse( unicode(configcontents, 'latin-1'))

                                # check for thumbnail
                                if tree.root.game.hasChild('thumbnailpath') and tree.root.game.thumbnailpath.getValue() != '':

                                    # make path
                                    thumbpath = tree.root.game.thumbnailpath.getValue()
                                    path = os.path.dirname(thumbpath)
                                    thumbname = os.path.basename(thumbpath)
                                    #os.makedirs( os.path.join(self.tempdir, path))

                                    if thumbpath in myzip.namelist():
                                        # extract thumbnail
                                        outfile = open(os.path.join(self.tempdir, thumbname), 'wb')
                                        outfile.write(myzip.read( thumbpath))
                                        outfile.close()

                                        image = wx.Image( os.path.join( self.tempdir, thumbname), wx.BITMAP_TYPE_ANY)
                                        image.Rescale(self.THUMBWIDTH, self.THUMBHEIGHT)
                                        imageindex[gamename] = image.SaveFile( os.path.join(self.thumbdir, gamename), wx.BITMAP_TYPE_JPEG)

                                        self.message(" - Using %s as thumbnail for %s" % (thumbname, gamename), self.DEBUG)
                                        thumbnailfound = True

                                    # try with folder name attached
                                    elif "%s/%s" % (gamename, thumbpath) in myzip.namelist():
                                        thumbpath = "%s/%s" % (gamename, thumbpath)

                                        # extract thumbnail
                                        outfile = open(os.path.join(self.tempdir, thumbname), 'wb')
                                        outfile.write(myzip.read( thumbpath))
                                        outfile.close()
                                        image = wx.Image( os.path.join( self.tempdir, thumbname), wx.BITMAP_TYPE_ANY)
                                        image.Rescale(self.THUMBWIDTH, self.THUMBHEIGHT)
                                        imageindex[gamename] = image.SaveFile( os.path.join(self.thumbdir, gamename), wx.BITMAP_TYPE_JPEG)

                                        self.message(" - Using %s as thumbnail for %s" % (thumbname, gamename), self.DEBUG)
                                        thumbnailfound = True

                                    else:
                                        self.message(" - %s in %s not found in zip" % (thumbpath, self.CONFIGFILE), self.DEBUG)

                                # fall back to first screenshots available
                                elif tree.root.game.hasChild('screenshot'):
                                    shot = tree.root.game.screenshot[0]
                                    if shot.hasChild('path') and shot.path.getValue() != '':
                                        thumbpath = shot.path.getValue()
                                        path = os.path.dirname(thumbpath)
                                        thumbname = os.path.basename(thumbpath)

                                        if thumbpath in myzip.namelist():
                                            # extract thumbnail
                                            outfile = open(os.path.join(self.tempdir, thumbname), 'wb')
                                            outfile.write(myzip.read(thumbpath))
                                            outfile.close()
                                            image = wx.Image( os.path.join( self.tempdir, thumbname), wx.BITMAP_TYPE_ANY)
                                            image.Rescale(self.THUMBWIDTH, self.THUMBHEIGHT)
                                            imageindex[gamename] = image.SaveFile( os.path.join(self.thumbdir, gamename), wx.BITMAP_TYPE_JPEG)

                                            self.message(" - Using screenshot %s as thumbnail for %s" % (thumbname, gamename), self.DEBUG)
                                            thumbnailfound = True

                                        # try adding folder name
                                        elif "%s/%s" % (gamename, thumbpath) in myzip.namelist():
                                            thumbpath = "%s/%s" % (gamename, thumbpath)
                                            # extract thumbnail
                                            outfile = open(os.path.join(self.tempdir, thumbname), 'wb')
                                            outfile.write(myzip.read(thumbpath))
                                            outfile.close()

                                            image = wx.Image( os.path.join( self.tempdir, thumbname), wx.BITMAP_TYPE_ANY)
                                            image.Rescale(self.THUMBWIDTH, self.THUMBHEIGHT)
                                            imageindex[gamename] = image.SaveFile( os.path.join(self.thumbdir, gamename), wx.BITMAP_TYPE_JPEG)

                                            self.message(" - Using screenshot %s as thumbnail for %s" % (thumbname, gamename), self.DEBUG)
                                            thumbnailfound = True
                                        else:
                                            self.message(" - %s in %s not found in zip" % (thumbpath, self.CONFIGFILE), self.DEBUG)

                            except Exception, e:
                                self.message(" - Exception when searching for thumbnail in %s of zipped %s" % (self.CONFIGFILE, gamename), self.DEBUG)
                                self.message(" - %s"%repr(e), self.DEBUG)
                                pass

                        # flip namelist to search from top directory down
                        namelist.reverse()

                        # no config, or no thumbnail/screenshots specified
                        if not thumbnailfound:

                            # look for thumb variations first
                            for fi in namelist:
                                path = os.path.dirname(fi)
                                thumbname = os.path.basename(fi)
                                if (thumbname.startswith(('thumbnail', 'thumb', 'THUMB', 'THUMBNAIL', 'icon', 'ICON'))
                                    and thumbname.endswith(('.png', '.PNG', '.gif', '.GIF', '.jpg', '.JPG', '.jpeg', '.JPEG'))):

                                    try:
                                        # extract thumbnail
                                        outfile = open(os.path.join(self.tempdir, thumbname), 'wb')
                                        outfile.write(myzip.read(fi))
                                        outfile.close()

                                        # save image in list
                                        image = wx.Image( os.path.join( self.tempdir, thumbname), wx.BITMAP_TYPE_ANY)
                                        image.Rescale(self.THUMBWIDTH, self.THUMBHEIGHT)
                                        imageindex[gamename] = image.SaveFile( os.path.join(self.thumbdir, gamename), wx.BITMAP_TYPE_JPEG)

                                        self.message(" - Found thumbnail %s for %s" % (thumbname, gamename), self.DEBUG)

                                        thumbnailfound = True
                                        break

                                    except Exception, e:
                                        self.message("Error while using %s as thumbnail"%thumbname, self.ERROR)
                                        self.message(repr(e), self.ERROR)

                        # if not found, search screenshots
                        if not thumbnailfound:

                            # try to use screenshot.* first
                            for fi in namelist:
                                path = os.path.dirname(fi)
                                thumbname = os.path.basename(fi)

                                if (thumbname.startswith(('screenshot', 'SCREENSHOT'))
                                    and thumbname.endswith(('.png', '.PNG', '.gif', '.GIF', '.jpg', '.JPG', '.jpeg', '.JPEG'))):

                                    try:
                                        # extract thumbnail
                                        outfile = open(os.path.join(self.tempdir, thumbname), 'wb')
                                        outfile.write(myzip.read(fi))
                                        outfile.close()

                                        # save image in list
                                        image = wx.Image( os.path.join( self.tempdir, thumbname), wx.BITMAP_TYPE_ANY)
                                        image.Rescale(self.THUMBWIDTH, self.THUMBHEIGHT)
                                        imageindex[gamename] = image.SaveFile( os.path.join(self.thumbdir, gamename), wx.BITMAP_TYPE_JPEG)

                                        self.message(" - Using main screenshot %s as thumbnail for %s" % (thumbname, gamename), self.DEBUG)

                                        thumbnailfound = True
                                        break

                                    except Exception, e:
                                        self.message("Error while using main screenshot %s as thumbnail"%thumbname, self.ERROR)
                                        self.message(repr(e), self.ERROR)

                        # still nothing, take any image at this point
                        if not thumbnailfound:

                            for fi in namelist:
                                path = os.path.dirname(fi)
                                thumbname = os.path.basename(fi)
                                if thumbname.endswith(('.png', '.PNG', '.gif', '.GIF', '.jpg', '.JPG', '.jpeg', '.JPEG')):

                                    try:
                                        # extract thumbnail
                                        outfile = open(os.path.join(self.tempdir, thumbname), 'wb')
                                        outfile.write(myzip.read(fi))
                                        outfile.close()

                                        # save image in list
                                        image = wx.Image( os.path.join( self.tempdir, thumbname), wx.BITMAP_TYPE_ANY)
                                        image.Rescale(self.THUMBWIDTH, self.THUMBHEIGHT)
                                        imageindex[gamename] = image.SaveFile( os.path.join(self.thumbdir, gamename), wx.BITMAP_TYPE_JPEG)

                                        self.message(" - Using last resort image %s as thumbnail for %s" % (thumbname, gamename), self.DEBUG)

                                        thumbnailfound = True
                                        break

                                    except Exception, e:
                                        self.message("Error while using image %s as thumbnail"%thumbname, self.ERROR)
                                        self.message(repr(e), self.ERROR)


        # add game names to list
        gamenames = self.games.keys()
        gamenames.sort()

        stylednames = []
        # style according to having been played
        for gamename in gamenames:

            if gamename.strip() in playedgames:
                color = self.PLAYEDCOLOR
            else:
                color = self.DEFAULTCOLOR

            if gamename in imageindex.keys():
                stylednames.append( (gamename, color, os.path.join(self.thumbdir, gamename)) )

            else:
                stylednames.append( (gamename, color))

        self.gamelist.InsertItems( stylednames, 0 )

    def sanitizePath(self, path):
        dirs = []

        # change back slashes to forward slashes
        filename = path.replace('\\', '/')

        # split on forward slashes
        items = filename.split('/')
        for item in items:
            dirs.append(item)

        # join up all the directories
        return os.path.sep.join(dirs)

    def loadGame(self, gamename):
        """Loads the specified game from the path found during loadDirectory."""

        if self.isunzipping:
            if time.time() - self.startedunzipping > self.UNZIPWAITTIME:
                self.writeLog('Previous game did not finish unzipping. Forcing it closed and loading new game')

            else:
                self.writeLog('Cannot load a new game until current game finishes unzipping. If not unzipping a game, restart CGL and report the bug.','warning')
                # reset game index
                self.gamelist.SetSelection(self.gameindex)
                return False

        if self.thread is not None:
            if self.thread.isAlive():
                self.writeLog("Cannot load a new game until you exit the currently running game. If not running a game, restart CGL and report the bug.", 'warning')
                # reset game index
                self.gamelist.SetSelection(self.gameindex)
                return False

        if gamename not in self.games:
            self.writeLog( 'Error: %s not found in games list' % gamename, self.ERROR)
            # reset game index
            self.gamelist.SetSelection(self.gameindex)
            return False

        # save new game index
        self.gameindex = self.gamelist.GetSelection()

        # enable play button
        self.playbutton.Enable(True)

        # reset game info
        self.gamename = gamename
        self.gamepath = ''
        self.gameentrypoint = ''
        readmefound = False
        licensefound = False
        self.gameentrypoints = {}

        # clear everything
        self.overviewtext.Clear()
        self.readmetext.Clear()
        self.licensetext.Clear()
        self.dependenciestext.Clear()
        self.screenshotspane.DestroyChildren()

        # if zip, unzip it
        if self.games[gamename].endswith( ('.zip', '.ZIP') ) and zipfile.is_zipfile(self.games[gamename]):

            # open zip for reading
            myzip = zipfile.ZipFile(self.games[gamename], 'r')

            # test the zip
            if myzip.testzip() is not None:
                self.message('Error: %s is corrupted. Try extracting manually' % f, self.ERROR)
                self.writeLog('Error: %s is corrupted. Try extracting manually' % f, 'error')
                return False

            else:
                if self.unzipToTemp( myzip, gamename):
                    folders = os.listdir( self.tempdir)
                    # change gamepath to first folder in tempdir
                    self.gamepath = os.path.join( self.tempdir, folders[0])
                else:
                    self.message('Error: Unknown error while extracting %s. Try extracting manually' % f, self.ERROR)
                    self.writeLog('Error: Unknown error while extracting %s. Try extracting manually' % f, 'error')
                    return False
        else:
            self.gamepath = self.games[gamename]

        # if CGL config found
        configprocessed = False
        if os.path.isfile( os.path.join( self.gamepath, self.CONFIGFILE)):
            kml = kml_single.KML()

            # change KML - read file ourselves to try to deal with unicode
            configfile = open( os.path.join(self.gamepath, self.CONFIGFILE))
            configcontents = configfile.read()
            tree = kml.parse( unicode(configcontents, 'latin-1'))

            boldfont = wx.Font(22, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)
            boldstyle = wx.TextAttr(wx.NamedColour('Black'))
            boldstyle.SetFont(boldfont)

            normalfont = wx.Font(18, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
            normalstyle = wx.TextAttr('Black')

            greenstyle = wx.TextAttr('Green')
            redstyle = wx.TextAttr('Red')

            try:
                game = tree.root.game

                # test for entry point
                if game.hasChild('entrypoint'):

                    # add entrypoints to dictionary
                    for ep in game.entrypoint:

                        # require label
                        if ep.hasChild('label') and ep.label.getValue() != '':
                            label = ep.label.getValue()
                        else:

                            # check if pre-beta.0.10 xml
                            entry = ''
                            # find entrypoint for this system and add to the dict
                            if sys.platform == 'win32':
                                if ep.hasChild('windows') and ep.windows.getValue().lower() not in ['', 'true', 'false']:
                                    entry = ep.windows.getValue()

                            elif sys.platform.startswith('darwin') or sys.platform.startswith('mac'):
                                if ep.hasChild('mac') and ep.mac.getValue().lower() not in ['', 'true', 'false']:
                                    entry = ep.mac.getValue()
                            else:
                                if ep.hasChild('linux') and ep.linux.getValue().lower() not in ['', 'true', 'false']:
                                    entry = ep.linux.getValue()

                            # is valid -- warn about new format and break loop
                            if entry != '' and os.path.isfile( os.path.join( self.gamepath, entry)):
                                self.message("Entrypoint format in %s is deprecated. Please inform the author to update their game information." % self.CONFIGFILE, self.WARNING)
                                self.gameentrypoints['Main'] = (os.path.join( self.gamepath, entry), False)
                                break

                            # not valid, assume it is bad attempt at new format, skip
                            else:
                                continue

                        # require path or webpath
                        if ep.hasChild('path') and ep.path.getValue() != '':
                            path = os.path.join( self.gamepath, ep.path.getValue())
                        else:
                            path = ''

                        if ep.hasChild('webpath') and ep.webpath.getValue():
                            path = ep.webpath.getValue()
                            webpath = True
                        else:
                            # make sure path is valid
                            if not os.path.isfile( os.path.join(self.gamepath, path)):
                                continue
                            webpath = False

                        # find entrypoint for this system and add to the dict
                        if sys.platform == 'win32':
                            if ep.hasChild('windows') and ep.windows.getValue().lower() != 'false':
                                self.gameentrypoints[label] = (path, webpath)

                        elif sys.platform.startswith('darwin') or sys.platform.startswith('mac'):
                            if ep.hasChild('mac') and ep.mac.getValue().lower() != 'false':
                                self.gameentrypoints[label] = (path, webpath)
                        else:
                            if ep.hasChild('linux') and ep.linux.getValue().lower() != 'false':
                                self.gameentrypoints[label] = (path, webpath)

                # language
                try:
                    language = game.language.getValue()
                except:
                    pass

                # load screenshots
                if game.hasChild('screenshot'):

                    sizer = wx.FlexGridSizer(0,1,30)

                    height = 30
                    for screenshottag in game.screenshot:
                        if screenshottag.hasChild('path') and screenshottag.path.getValue() != '' and os.path.isfile( os.path.join(self.gamepath, screenshottag.path.getValue())):

                            # sanitize path
                            screenshotpath = self.sanitizePath( os.path.join(self.gamepath, screenshottag.path.getValue()))

                            try:

                                sizer2 = wx.FlexGridSizer(0,1,5)

                                # screenshot
                                image = wx.Image( os.path.join( self.gamepath, screenshotpath), wx.BITMAP_TYPE_ANY).ConvertToBitmap()
                                sizer2.Add(wx.StaticBitmap(self.screenshotspane, -1, image, (20, height), (image.GetWidth(), image.GetHeight())))
                                height += 10 + image.GetHeight()

                                # title text
                                sizer2.Add(wx.StaticText(self.screenshotspane, -1, screenshottag.title.getValue(), (20, height)))
                                height += 10

                                # description text
                                desc = wx.StaticText(self.screenshotspane, -1, screenshottag.description.getValue(), (40, height))
                                desc.Wrap( max(300,image.GetWidth()) )
                                sizer2.Add(desc)
                                height += desc.GetSize()[1] + 50

                                sizer.Add(sizer2)

                            except Exception, e:
                                self.message(repr(e), self.ERROR)
                        else:
                            self.message("Screenshot not found", self.WARNING)

                    # make scrolling work
                    self.screenshotspane.SetSizer(sizer)
                    self.screenshotspane.SetAutoLayout(1)
                    self.screenshotspane.SetupScrolling()

                else:

                    # look for screenshots by extension
                    found = False
                    height = 30
                    sizer = wx.FlexGridSizer(1, 1, 30, 0)
                    sizer.AddGrowableCol(0)

                    for f in os.listdir( self.gamepath):
                        if f.endswith( ('.png', '.PNG', '.gif', '.GIF', '.jpp', '.JPG', '.jpeg', '.JPEG')) or f.startswith(('screenshot', 'SCREENSHOT')):
                            try:
                                image = wx.Image( os.path.join( self.gamepath, f), wx.BITMAP_TYPE_ANY).ConvertToBitmap()
                                sizer.Add(wx.StaticBitmap(self.screenshotspane, -1, image, (20, height), (image.GetWidth(), image.GetHeight())))
                                height += 10 + image.GetHeight()
                                found = True
                            except:
                                pass

                    # if screenshots found, add them
                    if found:
                        # make scrolling work
                        self.screenshotspane.SetSizer(sizer)
                        self.screenshotspane.SetAutoLayout(1)
                        self.screenshotspane.SetupScrolling()

                    # otherwise add message
                    else:
                        wx.StaticText(self.screenshotspane, -1, 'No Screenshots Found', (20, 10))

                # load dependencies
                if game.hasChild('dependency'):

                    self.addText(self.dependenciestext, "Dependencies:\n", boldstyle)

                    # go through each dependency
                    for deptag in game.dependency:

                        # name
                        depstring = deptag.name.getValue()

                        # version
                        if deptag.hasChild('version') and deptag.version.getValue() != '':
                            depstring += ' - V %s' % deptag.version.getValue()

                        # url
                        if deptag.hasChild('url') and deptag.url.getValue() != '':
                            depstring += ' - %s' % deptag.url.getValue()

                        # add string to text area
                        self.addText(self.dependenciestext,"%s"% depstring, normalstyle)

                        # if python, try importing modules
                        if language in ['python', 'Python', 'PYTHON']:
                            if deptag.hasChild('modulename') and deptag.modulename.getValue() != '':
                                modulename = deptag.modulename.getValue()
                                try:
                                    __import__(modulename)
                                    self.addText(self.dependenciestext, ' - Module Found', greenstyle)
                                except Exception, e:
                                    self.addText(self.dependenciestext, ' - Module Not Found', redstyle)

                        # line break
                        self.addText(self.dependenciestext, '\n', normalstyle)

                else:
                    self.addText(self.dependenciestext, 'Dependencies Not Listed', normalstyle)

                # overview page
                # game name
                if game.hasChild('name'):
                    name = game.name.getValue()
                else:
                    name = gamename

                # game version
                if game.hasChild('version'):
                    version = ' - V %s' % game.version.getValue()
                else:
                    version = ''

                # game URL
                if game.hasChild('url'):
                    url = "Project URL: %s\n\n" % game.url.getValue()
                else:
                    url = ''

                #  entry page / judging page
                if game.hasChild('judgeurl') and game.judgeurl.getValue() != '':
                    judgeurl = "Judging URL: %s\n\n" % game.judgeurl.getValue()
                else:
                    judgeurl = ''

                # authors
                authorstring = ''
                if game.hasChild('author'):
                    for authornode in game.author:
                        if authornode.hasChild('name'):
                            string = authornode.name.getValue()

                            if authornode.hasChild('url') and authornode.url.getValue() != '':
                                string += ' - %s' % authornode.url.getValue()

                            if authornode.hasChild('contribution') and authornode.contribution.getValue() != '':
                                string += ' - %s' % authornode.contribution.getValue()

                            authorstring += "%s \n" % string

                # notes
                if game.hasChild('notes'):
                    notes = game.notes.getValue()
                else:
                    notes = ''

                # set overview text
                self.overviewtext.Clear()

                # name and version
                self.addText(self.overviewtext, "%s %s\n" % (name, version), boldstyle)

                # url
                self.addText(self.overviewtext, "%s"%url, normalstyle)

                # judge url
                self.addText(self.overviewtext, "%s"%judgeurl, normalstyle)

                # authors
                self.addText(self.overviewtext, "Authors:\n%s\n\n" % authorstring, normalstyle)

                # notes
                self.addText(self.overviewtext, "Notes:\n%s" % notes, normalstyle)

                # see if readme path is specified
                if game.hasChild('readmepath'):
                    readmepath = self.sanitizePath(game.readmepath.getValue())
                    if os.path.isfile( os.path.join(self.gamepath, readmepath)):
                        readme = file( os.path.join(self.gamepath, readmepath))
                        text = readme.read()
                        self.readmetext.AppendText(unicode(text, 'latin-1'))
                        readme.close()
                        self.readmetext.ShowPosition(0)
                        readmefound = True

                # see if license path is specified
                if game.hasChild('licensepath'):
                    licensepath = self.sanitizePath(game.licensepath.getValue())
                    if os.path.isfile( os.path.join(self.gamepath, licensepath)):
                        lic = file( os.path.join(self.gamepath, licensepath))
                        text = lic.read()
                        self.licensetext.AppendText(unicode(text, 'latin-1'))
                        lic.close()
                        self.licensetext.ShowPosition(0)
                        licensefound = True

                configprocessed = True

            except Exception, e:
                self.message('Error while parsing %s' % self.CONFIGFILE, self.ERROR)
                self.message(repr(e), self.ERROR)
                configprocessed = False

        # no config found
        if not configprocessed:
            # set overview text
            self.overviewtext.Clear()

            # name
            boldfont = wx.Font(22, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)
            boldstyle = wx.TextAttr(wx.NamedColour('Black'))
            boldstyle.SetFont(boldfont)

            self.addText(self.overviewtext, "%s\n" % gamename, boldstyle)
            self.addText(self.overviewtext, "No %s file provided" % self.CONFIGFILE, wx.TextAttr('Black'))

            # look for screenshots by extension
            found = False
            height = 30
            sizer = wx.FlexGridSizer(1, 1, 30, 0)
            sizer.AddGrowableCol(0)

            for f in os.listdir( self.gamepath):
                if f.endswith( ('.png', '.PNG', '.gif', '.GIF', '.jpp', '.JPG', '.jpeg', '.JPEG')) or f.startswith(('screenshot', 'SCREENSHOT')):
                    print 'found screenshot %s' % f
                    try:
                        image = wx.Image( os.path.join( self.gamepath, f), wx.BITMAP_TYPE_ANY).ConvertToBitmap()
                        sizer.Add(wx.StaticBitmap(self.screenshotspane, -1, image, (20, height), (image.GetWidth(), image.GetHeight())))
                        height += 10 + image.GetHeight()
                        found = True
                    except:
                        pass

            # if screenshots found, add them
            if found:
                # make scrolling work
                self.screenshotspane.SetSizer(sizer)
                self.screenshotspane.SetAutoLayout(1)
                self.screenshotspane.SetupScrolling()

            # otherwise add message
            else:
                wx.StaticText(self.screenshotspane, -1, 'No Screenshots Found', (20, 10))

        if not readmefound:
            # look for readme and all its variations
            readmes = ['readme', 'README', 'Readme']
            extensions = ['', '.txt', '.TXT', '.rtf', '.RTF', '.doc', '.DOC']

            found = False
            for name in readmes:
                # break if found a readme in extension loop
                if found:
                    break

                for extension in extensions:
                    # if readme available
                    if os.path.isfile( os.path.join( self.gamepath, name+extension) ):
                        # put contents in readme tab
                        readme = file(os.path.join( self.gamepath, name+extension), 'r')
                        readmetext = readme.read()
                        self.readmetext.AppendText( unicode(readmetext, 'latin-1'))
                        readme.close()
                        self.readmetext.ShowPosition(0)
                        found = True
                        break

            if not found:
                self.readmetext.AppendText("Readme not found")

        if not licensefound:
            # look for license and all its variations
            licenses = ['license', 'LICENSE', 'legal', 'LEGAL']
            extensions = ['', '.txt', '.TXT', '.rtf', '.RTF', '.doc', '.DOC']

            found = False
            for name in licenses:
                for extension in extensions:
                    # break if found a license in extension loop
                    if found:
                        break

                    # if file found
                    if os.path.isfile( os.path.join( self.gamepath, name+extension) ):
                        # put contents in license tab
                        lic = file(os.path.join( self.gamepath, name+extension), 'r')
                        text = lic.read()
                        self.licensetext.AppendText( unicode(text, 'latin-1'))
                        lic.close()
                        self.licensetext.ShowPosition(0)
                        found = True
                        break

            if not found:
                self.licensetext.AppendText("License not found")

        # show overview pane
        self.notebook_1.SetSelection(0)

    def OnURL(self, event):
        if event.MouseEvent.LeftDown():
            url = event.GetEventObject().GetValue()[event.GetURLStart(): event.GetURLEnd()]
            wx.LaunchDefaultBrowser(url)

        event.Skip()

    def addText(self, widget, text, style):
        """Convenience function for appending text with the specified style."""
        widget.AppendText(text)
        index = widget.GetLastPosition()
        widget.SetStyle(index-len(text), index, style)
        widget.SetInsertionPointEnd()

    def playGame(self):
        """Attempts to play the current game."""

        # if no game entry points available, use dialog
        if len(self.gameentrypoints) == 0:
            fileDiag = wx.FileDialog(
                    self, message="Select game entry point",
                    defaultDir= self.gamepath,
                    wildcard="All files (*)|*",
                    style=wx.CHANGE_DIR #| wx.FD_SAVE
                    )

            # Show the dialog and retrieve the user response.
            if fileDiag.ShowModal() == wx.ID_OK:
                self.gameentrypoint = fileDiag.GetPath()
                webpath = False

                self.message("User selected %s from file dialog" % self.gameentrypoint, self.DEBUG)

            # user clicked cancel - cancel everything
            else:
                fileDiag.Destroy()
                return False

        # if more than one entry point, show dialog for user to decide
        elif len(self.gameentrypoints) > 1:

            entrypointlabels = []
            for label, (path, webpath) in self.gameentrypoints.iteritems():
                if webpath:
                    entrypointlabels.append("%s (web)" % label)
                else:
                    entrypointlabels.append(label)

            dlg = wx.SingleChoiceDialog(
                self, 'Select which entry point to use', 'Select Game Entry Point',
                entrypointlabels,
                wx.CHOICEDLG_STYLE
                )

            if dlg.ShowModal() == wx.ID_OK:
                choice = dlg.GetStringSelection()
                dlg.Destroy()

            else:
                dlg.Destroy()
                return False

            # clean choice if web
            if choice.endswith( ' (web)'):
                choice = choice[:-6]

            # get info for user's choice
            self.gameentrypoint, webpath = self.gameentrypoints[choice]

            self.message("User selected %s from entry point options" % self.gameentrypoint, self.DEBUG)

        # only one entry point found, use it
        else:
            self.gameentrypoint, webpath = self.gameentrypoints.values()[0]

        # mark as played
        self.gamelist.SetColor( self.gamelist.GetSelection(), self.PLAYEDCOLOR)

        if os.path.isfile( os.path.join(self.currentdir, self.DIRCONFIGFILE)):
            dirconfig = file(os.path.join(self.currentdir, self.DIRCONFIGFILE),'a+')
            playedgames = dirconfig.readlines()
            found = False
            for game in playedgames:
                if self.gamename.strip() == game.strip():
                    found = True
                    break
            if not found:
                dirconfig.write( "%s\n"%self.gamename )
            dirconfig.close()

        # if game is a webpage, load it
        if webpath:
            wx.LaunchDefaultBrowser(self.gameentrypoint)

        # game is local file
        else:

            # some beginning info
            self.writeLog(" "*60)
            self.writeLog(" "*60)
            self.writeLog("Running %s" % self.gameentrypoint)
            self.writeLog("-"*60)

            # run game in a thread
            self.thread = threading.Thread(target=self.playInThread)
            self.thread.start()

    def playInThread(self):

        try:
            # try changing CWD
            try:
                os.chdir( os.path.dirname(self.gameentrypoint) )
            except:
                self.message("Couldn't change working directory to %s"%os.path.dirname(self.gameentrypoint))

            # test if path has spaces
            if self.gameentrypoint.strip().find(' ') != -1:
                # wrap in quotes
                self.gameentrypoint = '"%s"' % self.gameentrypoint.strip()

            self.message("Running %s in thread" % self.gameentrypoint, self.DEBUG)

            # run and divert output to outputtext if possible
            proc = subprocess.Popen( self.gameentrypoint,
                            shell=True,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE)

            stdout, stderr = proc.communicate()
            outsplit = repr(stdout).split('\\r\\n')
            for line in outsplit:
                line = line.replace('\\r\\n', '')
                line = line.replace('\\n', '')
                self.writeLog(line)

            if len(repr(stderr)) > 2:
                self.writeLog(repr(stderr))
                self.message('Error: %s' % repr(stderr), self.ERROR)

        except Exception, e:
            self.writeLog("There was an error while running the game", 'error')
            self.message("There was an error while running the game", self.ERROR)
            self.message( repr(e), self.ERROR )

        # change CWD back to cgl so temp folders can get cleaned up
        os.chdir(self.cglpath)

    def message(self, message, verbosity):
        if verbosity <= self.verbosity:

            br = """
"""
            message = message.replace("\\r\\n", br)
            message = message.replace("\\n", br)

            print message

    def writeLog(self, message, messagetype='default'):
        """Appends the line to the output text control"""
        if len(message) > 0:
            br = """
"""
            message = message.replace( "\\r\\n", br)
            message = message.replace( "\\n", br)

            self.consoletext.AppendText(message + br)
            current = self.consoletext.GetLastPosition()

            if messagetype == 'error':
                self.consoletext.SetStyle( current - len(message) - 1, current, wx.TextAttr('RED'))
            else:
                self.consoletext.SetStyle( current - len(message) - 1, current, wx.TextAttr('BLACK'))

            # scroll to beginning, then end -- render bug
            self.consoletext.SetInsertionPoint(0)
            self.consoletext.SetInsertionPointEnd()

    def findThumbnail(self, path):
        """Tries to find a suitable thumbnail in the specified path"""

        # look for config file in path
        if os.path.isfile( os.path.join(path, self.CONFIGFILE)):

            try:
                # if found, load
                configfile = open( os.path.join(self.path, self.CONFIGFILE))
                configcontents = configfile.read()
                tree = kml.parse( unicode(configcontents, 'latin-1'))

                # if has thumbnail and is valid
                if tree.root.cgl.hasChild('thumbnailpath') and os.path.isfile( os.path.join.path, tree.root.cgl.thumbnailpath.getValue()):
                    # use it
                    image = wx.Image( os.path.join( path, tree.root.cgl.thumbnailpath.getValue()), wx.BITMAP_TYPE_ANY)
                    image.Rescale(self.THUMBWIDTH, self.THUMBHEIGHT)
                    return image
            except:
                pass

        # look in main folder
        try:
            # looks for a thumbnail in the path
            for f in os.listdir(path):
                if (f.startswith(('thumbnail', 'thumb', 'THUMB', 'THUMBNAIL', 'icon', 'ICON'))
                    and f.endswith(('.png', '.PNG', '.gif', '.GIF', '.jpg', '.JPG', '.jpeg', '.JPEG'))):
                    image = wx.Image( os.path.join( path, f), wx.BITMAP_TYPE_ANY)
                    image.Rescale(self.THUMBWIDTH, self.THUMBHEIGHT)
                    return image

            # if not found, search screenshots

            # try to use screenshot.* first
            for f in os.listdir(path):
                if (f.startswith(('screenshot', 'SCREENSHOT'))
                    and f.endswith(('.png', '.PNG', '.gif', '.GIF', '.jpg', '.JPG', '.jpeg', '.JPEG'))):
                    image = wx.Image( os.path.join( path, f), wx.BITMAP_TYPE_ANY)
                    image.Rescale(self.THUMBWIDTH, self.THUMBHEIGHT)
                    return image

            # still nothing, take any image at this point
            for f in os.listdir(path):
                if f.endswith(('.png', '.PNG', '.gif', '.GIF', '.jpg', '.JPG', '.jpeg', '.JPEG')):
                    image = wx.Image( os.path.join( path, f), wx.BITMAP_TYPE_ANY)
                    image.Rescale(self.THUMBWIDTH, self.THUMBHEIGHT)
                    return image
        except:
            pass

        return None

    def unzipToTemp(self, myzip, gamename):
        """Unzips the zip file to a temporary directory.
        Zip functionality from article by Doug Tolton
        U{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252508}"""

        # save that we are unzipping so no other games get unzipped until we are finished
        self.isunzipping = True
        self.startedunzipping = time.time()

        # create temp directory if necessary
        if self.tempdir == '':
            self.tempdir = tempfile.mkdtemp(prefix='cgl_')

        # delete files inside if already exists
        else:
            for f in os.listdir(self.tempdir):
                if os.path.isfile( os.path.join(self.tempdir, f)):
                    os.remove(os.path.join(self.tempdir, f))
                elif os.path.isdir( os.path.join(self.tempdir, f)):
                    shutil.rmtree(os.path.join(self.tempdir, f))

        # progress info
        namelist = myzip.namelist()
        numfiles = len(namelist)
        tick = 100.0/ numfiles          # so we dont have to do division in every iteration of the loop
        progress = 0.0

        # write to log
        self.writeLog("Unzipping %s" % self.gamename)
        logstartpoint = self.consoletext.GetInsertionPoint()

        for filename in namelist:
            # files
            if filename.endswith('/'):
                if not os.path.exists( os.path.join(self.tempdir, filename)):
                    os.makedirs( os.path.join(self.tempdir, filename))

                    # calculate progress
                    progress += tick

            else:
                # make sure base dir exists -- this should never happen
                basedir = os.path.join( self.tempdir, os.path.dirname(filename))
                if not os.path.isdir(basedir):
                    os.makedirs(basedir)

                outfile = open(os.path.join(self.tempdir, self.sanitizePath(filename)), 'wb+')
                outfile.write(myzip.read(filename))
                #outfile.flush()
                outfile.close()

                # molest permissions
                os.chmod( os.path.join(self.tempdir, self.sanitizePath(filename)), stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)

                # calculate progress
                progress += tick

                # show progress -- getting fancy with the textctrl
                self.consoletext.Replace( logstartpoint, self.consoletext.GetLastPosition(), '%d%% Complete' % progress)

        outfile = None

        # make sure it finishes at 100% :D
        self.consoletext.Replace( logstartpoint, self.consoletext.GetLastPosition(), '100% Complete\n')

        # test if only one folder -- if not, author didnt zip a containing folder... jerk :D
        dirlist = os.listdir(self.tempdir)
        if len(dirlist) > 1:
            # make folder
            os.mkdir( os.path.join(self.tempdir, gamename))

            # move the files into it
            for f in dirlist:
                shutil.move( os.path.join(self.tempdir, f), os.path.join(self.tempdir, gamename, f))

        self.isunzipping = False
        return True

    def checkVersion(self):
        try:
            # if it has been more than a week since the last check
            lastupdate = self.configtree.root.cgl.lastupdate.getValue()

            # convert for comparison?
            parsedtime = time.mktime(time.strptime( lastupdate, '%Y-%m-%d'))

            # calculate time since last update
            timesince = time.mktime(time.localtime()) - parsedtime


            if timesince > self.UPDATECHECKFREQUENCY:
                # show popup to confirm user wants to access the net
                dlg = wx.MessageDialog(self, "Do you want CGL to check for updates now?",
                                   'Check for Updates?',
                                   #wx.OK | wx.ICON_INFORMATION
                                   wx.YES_NO #| wx.NO_DEFAULT | wx.CANCEL | wx.ICON_INFORMATION
                                   )
                choice = dlg.ShowModal()
                dlg.Destroy()

                # if user wants to check
                if choice == wx.ID_YES:

                    # check URL
                    request = urllib2.Request(self.VERSIONCHECKPATH)
                    page = urllib2.urlopen(request)

                    #parse page
                    content = page.read()

                    # parse response
                    kml = kml_single.KML()
                    tree = kml.parse(content)

                    app = tree.root.application
                    version = app.version.getValue()
                    url = app.url.getValue()

                    # if version is different, show popup
                    if version.lower() != self.VERSION.lower():
                        versionmessage = """
A new version of CGL is available.
Your current version is %s. The latest available version is %s.
You can download the new version at:
%s""" % (self.VERSION, version, url)
                        dlg = wx.MessageDialog(self, versionmessage,
                                       'A new version is available',
                                       wx.OK | wx.ICON_INFORMATION
                                       #wx.YES_NO | wx.NO_DEFAULT | wx.CANCEL | wx.ICON_INFORMATION
                                       )
                        dlg.ShowModal()
                        dlg.Destroy()

                    # otherwise, write to log
                    else:
                        self.writeLog("CGL is up to date")

            # reset update time
            self.configtree.root.cgl.lastupdate.setValue( time.strftime('%Y-%m-%d'))
            self.saveConfig()

        except Exception, e:
            self.message('Failed to check version', self.WARNING)
            self.message( repr(e), self.WARNING)



# end of class MainFrame


if __name__ == "__main__":
    app = wx.PySimpleApp(0)
    wx.InitAllImageHandlers()
    mainFrame = MainFrame(None, -1, "")
    app.SetTopWindow(mainFrame)
    mainFrame.Show()
    app.MainLoop()
